#include <os/mm.h>
#include <os/lock.h>
#include <os/time.h>
#include <os/irq.h>
#include <screen.h>
#include <stdio.h>
#include <assert.h>
#include <os/queue.h>
#include <os/sched.h>
#include <pgtable.h>
#include <user_programs.h>
#include <os/elf.h>
#include <os/string.h>
#include <emacps/xemacps_example.h>
#include <emacps/xemacps_hw.h>
#include <emacps/xemacps_bdring.h>
#include <emacps/xparameters.h>
#include <emacps/xemacps.h>
//extern queue_t sleep_queue;
extern queue_t ready_queue;
extern queue_t net_recv_queue;
extern queue_t net_send_queue;
extern XEmacPs_Bd tx_bdspace[32];
extern XEmacPs_Bd rx_bdspace[32] __attribute__((aligned(64)));
extern void __global_pointer$();
pcb_t pcb[NUM_MAX_TASK];

const ptr_t pid0_stack = INIT_KERNEL_STACK + PAGE_SIZE;
pcb_t pid0_pcb = {
    .pid = 0,
    .kernel_sp = (ptr_t)pid0_stack,
    .user_sp = (ptr_t)pid0_stack,
    .preempt_count = 0
};
/* current running task PCB */
//extern pcb_t * volatile current_running;
extern int rxbd_tail;
extern XEmacPs EmacPsInstance;
void net_check(){
    if(rx_bdspace[rxbd_tail-1][0]&1 != 0)
        do_unblock(&net_recv_queue);
    u32 txsr_reg = XEmacPs_ReadReg(EmacPsInstance.Config.BaseAddress,
                    XEMACPS_TXSR_OFFSET);
    if(txsr_reg&XEMACPS_TXSR_TXCOMPL_MASK != 0)
        do_unblock(&net_send_queue);
}
/* global process id */
pid_t process_id = 1;
void timer_check()
{
    int current=get_timer();
    for(int i=1;i<NUM_MAX_TASK;i++){
        if(current >= pcb[i].ddl && pcb[i].status==TASK_SLEEPING){
            pcb[i].status = TASK_READY;
            pcb[i].ddl = 0;
            queue_push(&ready_queue,(void *)(&pcb[i]));

        }
    }
}

void measure_cost(uint64_t time){
    vt100_move_cursor(1,9);
    screen_cursor_x = 1;
    screen_cursor_y = 9;
    printk("> [TEST] Time cost of do_scheduler is %ld ticks.\n\r",time);
    vt100_move_cursor(current_running->cursor_x,current_running->cursor_y);
    screen_cursor_x = current_running->cursor_x;
    screen_cursor_y = current_running->cursor_y;   
}

/*void scheduler(void)
{
    // Modify the current_running pointer.
    int time_elapsed = get_ticks();
    int rand=time_elapsed % 6;
    if(current_running->status!=TASK_BLOCKED){
      current_running->status = TASK_READY;
    if(current_running->pid!=1)
        queue_push(current_queue,(void *)current_running);
    }
    queue_t *old=current_queue;
    if( rand > 2)
      current_queue=&ready_queue[0];
    else if(rand > 0)
      current_queue=&ready_queue[1];
    else
      current_queue=&ready_queue[2];
    if(!queue_is_empty(current_queue))
      current_running=(pcb_t *)queue_dequeue(current_queue);
    else
      current_queue = old;
    current_running->status=TASK_RUNNING;
    vt100_move_cursor(current_running->cursor_x,
                      current_running->cursor_y);
    screen_cursor_x = current_running->cursor_x;
    screen_cursor_y = current_running->cursor_y; 
}*/
void test(int sepc){
    printk("sepc:0x%x\r",sepc);
}
void test1(int scause,int sp,int sepc){
    printk("scause:%d sp:0x%x sepc:0x%x\r",scause,sp,sepc);
}
void scheduler(void)
{
    // Modify the current_running pointer.
    //timer_check();
    if(current_running->pid != 0){
        if(current_running->status != TASK_BLOCKED && current_running->status!=TASK_EXITED && current_running->status!=TASK_SLEEPING){
            current_running->status = TASK_READY;
		    queue_push(&ready_queue,(void *)current_running);
            current_running->in_queue = &ready_queue;
        }
    }   
    if(!queue_is_empty(&ready_queue)){
		current_running=(pcb_t *)queue_dequeue(&ready_queue);
        current_running->in_queue = NULL;
    }
    current_running->status=TASK_RUNNING;
    // restore the current_runnint's cursor_x and cursor_y
    vt100_move_cursor(current_running->cursor_x,
                      current_running->cursor_y);
    screen_cursor_x = current_running->cursor_x;
    screen_cursor_y = current_running->cursor_y;
    set_satp(SATP_MODE_SV39,current_running->pid,(kva2pa(current_running->pgtable))>>NORMAL_PAGE_SHIFT);
    local_flush_tlb_all();
    //printk("current process:%d usr_stack:0x%lx entrypoint:0x%lx\n\r",current_running->pid,walkaddr(current_running->user_sp-NORMAL_PAGE_SIZE,current_running->pgtable),current_running->user_context.sepc);
}
void do_sleep(uint32_t sleep_time)
{
    if(sleep_time==0)
        return;
    current_running->status = TASK_SLEEPING;
    current_running->ddl = get_timer()+sleep_time;
    //queue_push(&sleep_queue,current_running);
    do_scheduler();
    
}
extern void ret_from_exception();
void do_block(queue_t *queue)
{
    // TODO: block the pcb task into the block queue
    current_running->status=TASK_BLOCKED;
    queue_push(queue,(void *)current_running);
    current_running->in_queue = queue;
    do_scheduler();
}

void do_unblock(queue_t *queue)
{
    // TODO: unblock the `pcb` from the block queue
    pcb_t *p;
    try_again:
    if(!queue_is_empty(queue)){
        p=(pcb_t *)queue_dequeue(queue);
        if(p->status==TASK_EXITED)
            goto try_again;
        p->status=TASK_READY;
        queue_push(&ready_queue,(void *)p);
        p->in_queue = &ready_queue;
    }
}

pid_t get_newpcb(){
    for (int i = 1; i < NUM_MAX_TASK; ++i)
    {
        if(pcb[i].status==TASK_EXITED)
            return i;
    }
}

pid_t do_spawn(task_info_t *task, void* arg, spawn_mode_t mode){
    int i=get_newpcb();
    pcb[i].pid= process_id++;
    pcb[i].preempt_count=1;
    pcb[i].kernel_sp = pcb[i-1].kernel_sp+4096*2;
    pcb[i].user_sp = pcb[i-1].user_sp+4096*2;
    for(int j=0;j<32;j++)
        pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1]=(uintptr_t)ret_from_exception;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
    pcb[i].kernel_context.regs[10]=arg;
    pcb[i].kernel_context.sstatus=34;
    queue_init(&pcb[i].wait_queue);
    pcb[i].kernel_context.sepc=(uintptr_t)task->entry_point;
    for(int j=0;j<32;j++)
        pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=(uintptr_t)ret_from_exception;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.regs[10]=arg;
    pcb[i].user_context.sstatus=34;
    pcb[i].user_context.sepc=(uintptr_t)task->entry_point;
    pcb[i].status = TASK_READY;
    pcb[i].type = task->type;
    queue_push(&ready_queue,(void *)&pcb[i]);
    pcb[i].in_queue = &ready_queue;
    pcb[i].cursor_x=0;
    pcb[i].cursor_y=0;
    return pcb[i].pid;
}
void do_exit(void){
    current_running->status=TASK_EXITED;
    while(!queue_is_empty(&(current_running->wait_queue))){
        do_unblock(&(current_running->wait_queue));
    }
    while(!queue_is_empty(&(current_running->lock_queue))){
        mutex_lock_t *lock=(mutex_lock_t*)queue_dequeue(&(current_running->lock_queue));
        do_mutex_lock_release(lock);
    }
    do_scheduler();
}
void do_clear()
{
    screen_clear();
}
int do_kill(pid_t pid){
    for (int i = 0; i < NUM_MAX_TASK; ++i)
    {
        if(pcb[i].pid==pid){
            pcb[i].status=TASK_EXITED;
            while(!queue_is_empty(&pcb[i].wait_queue)){
                do_unblock(&pcb[i].wait_queue);
            }
            while(!queue_is_empty(&(current_running->lock_queue))){
                mutex_lock_t *lock=(mutex_lock_t*)queue_dequeue(&(current_running->lock_queue));
                do_mutex_lock_release(lock);
            }

            if(&pcb[i]==current_running)
                do_scheduler();
            else
                queue_remove(&ready_queue,&pcb[i]);
            break;
        }
    }
}
int do_waitpid(pid_t pid){
    int i=0;
    for (i = 0; i < NUM_MAX_TASK; ++i)
    {
        if(pcb[i].pid==pid)
            break;
    }
    if(pcb[i].status==TASK_EXITED)
        return 0;
    do_block(&(pcb[i].wait_queue));
    return 1;
}
void do_process_show(){
    int i=0,j=0;
    for(j=0;j<process_id;j++){
        if(pcb[j].status==TASK_RUNNING){
            prints("[%d] PID: %d STATUS: TASK_RUNNING\n\r",i,pcb[j].pid);
            i++;
        }
        else if(pcb[j].status==TASK_BLOCKED&&pcb[j].pid!=0){
            prints("[%d] PID: %d STATUS: TASK_BLOCKED\n\r",i,pcb[j].pid);
            i++; 
        }
        else if(pcb[j].status==TASK_READY){
            prints("[%d] PID: %d STATUS: TASK_READY\n\r",i,pcb[j].pid);
            i++; 
        }
    }
}
pid_t do_getpid(){
    return current_running->pid;
}

pid_t do_exec(const char* file_name, int argc, char* argv[], spawn_mode_t mode){
    //printk("enter do_exec !!\n\r");
    /*if(strcmp(file_name,"recv")==0){
        recv_s(argc,argv);
        return 3;
    }*/
    int argc_c =argc;
    int i=get_newpcb();
    pcb[i].preempt_count=1;
    pcb[i].pid= process_id++;
    init_pgtable(&(pcb[i].pgtable),&(pcb[i].kernel_sp));
    //printk("init pgtable !!\n\r");
    pcb[i].user_sp = USER_STACK_ADDR;
    uint64_t user_stacktop;
    user_stacktop = walkaddr((uint64_t)USER_STACK_ADDR-NORMAL_PAGE_SIZE,(PTE *)pcb[i].pgtable);
    //printk("user stack:0x%lx\n\r",user_stacktop);
    char* shell=(char *)kmalloc_Page();
    char **binary;
    binary=&shell;
    int len=0;
    int *length=&len;
    uint64_t entrypoint;
    if(get_elf_file(file_name,binary,length)){
        //printk("find file!\n\r");
        entrypoint = load_elf(*binary,*length,pcb[i].pgtable,walkaddr);
        //printk("entrypoint:%x\n\r",entrypoint);
    }else
        printk("Load file failed !!\n\r");
    for(int j=0;j<32;j++)
        pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1]=(uintptr_t)&ret_from_exception;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
    pcb[i].kernel_context.regs[10]=(uint64_t)argc_c;
    pcb[i].kernel_context.regs[11]=(uint64_t)USER_STACK_ADDR-NORMAL_PAGE_SIZE;
    pcb[i].kernel_context.sstatus=0x40022;
    queue_init(&pcb[i].wait_queue);
    pcb[i].kernel_context.sepc=(uintptr_t)entrypoint;
    for(int j=0;j<32;j++)
        pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=(uintptr_t)&ret_from_exception;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.regs[10]=(uint64_t)argc_c;
    pcb[i].user_context.regs[11]=(uint64_t)USER_STACK_ADDR-NORMAL_PAGE_SIZE;
    pcb[i].user_context.sstatus=0x40022;
    pcb[i].user_context.sepc=(uintptr_t)entrypoint;
    pcb[i].status = TASK_READY;
    pcb[i].type = USER_PROCESS;
    queue_push(&ready_queue,(void *)&pcb[i]);
    //printk("init pcb!\n\r");
    char * ptr;
    ptr = (char *)USER_STACK_ADDR-NORMAL_PAGE_SIZE+128;
    char *argv_new[argc_c];
    for(int i=0;i<argc_c;i++){
        char *ptr_pa = (char *)user_stacktop+(((uint64_t)ptr<<52)>>52);
        memcpy(ptr_pa,argv[i],strlen(argv[i])+1);
        argv_new[i]=ptr;
        ptr+=strlen(argv[i])+1;
    }
    memcpy((char *)user_stacktop,argv_new,sizeof(char *)*argc);
    //printk("memcpy argv success!\n\r");
    pcb[i].cursor_x=0;
    pcb[i].cursor_y=0;
    pcb[i].exit_mode = mode;
    return pcb[i].pid;
}
extern ElfFile elf_files[];
void do_show_exec(){
    int i=0;
    int n=sizeof(elf_files)/sizeof(ElfFile);
    for(i=1;i<n;i++){
        prints("%s",elf_files[i].file_name);
        if(i!=n-1)
            prints("\t");
        else
            prints("\n");
    }
    return;
}
